package com.distributeredis.redis_springboot.springcache.controller;

import com.distributeredis.redis_springboot.springcache.service.UserService;
import com.distributeredis.redis_springboot.springcache.vo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;


/**
 * https://blog.csdn.net/zlj1217/article/details/80928122?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170217131316800185850107%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=170217131316800185850107&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~hot_rank-10-80928122-null-null.142^v96^pc_search_result_base7&utm_term=springcache%E5%AD%A6%E4%B9%A0&spm=1018.2226.3001.4187
 * spring boot cache 提供了一些注解方便做cache应用。
 *
 * （1）@CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置
 *
 * （2）@Cacheable:主要方法返回值加入缓存。同时在查询时，会先从缓存中取，若不存在才再发起对数据库的访问。
 *
 * （3）@CachePut:配置于函数上，能够根据参数定义条件进行缓存，与@Cacheable不同的是，每次回真实调用函数，所以主要用于数据新增和修改操作上。
 *
 * （4）@CacheEvict:配置于函数上，通常用在删除方法上，用来从缓存中移除对应数据
 *
 * （5）@Caching:配置于函数上，组合多个Cache注解使用。
 *
 * @EnableCaching 开启缓存注解功能
 * @Cacheable 在方法执行前spring先查看缓存中是否有数据，如果有数据，则直接返回缓存数据
 * 若没有数据，调用方法并将方法返回值放到缓存中
 * @CachePut 将方法的返回值放到缓存中
 * @CacheEvict 将一条或多条数据从缓存中删除
 *
 * 在spring boot项目中，使用缓存技术只需在项目中导入相关缓存技术的依赖包，并在启动类上使用@EnableCaching开启缓存支持即可。
 */
@RestController
@RequestMapping("/cache")
@Slf4j
public class UserController {

    @Autowired
    private CacheManager cacheManager;
    @Autowired
    private UserService userService;


    /**
     * 当前UserController的save方法是用来保存用户信息的，
     * 我们希望在该用户信息保存到数据库的同时，
     * 也往缓存中缓存一份数据，我们可以在save方法上加上注解 @CachePut，
     * 用法如下：
     *
     * @CachePut注解说明
     *      作用：将方法返回值，放入缓存
     *      value：缓存的名称，每个缓存名称下面可以有很多key
     *      key：缓存的key-----》支持spring的表达式语言spel语法
     *
     *      key的写法如下
     *      #user.id: #user指的是方法形参的名称，id指的是user的id属性，
     *      也就是使用user的id属性作为key；
     *      #user.name: #user指的是方法形参的名称，name指的是user的name
     *      属性，也就是使用user的name属性作为key
     *
     *      #result.id : #result代表方法返回值，该表达式 代表以返回对象的id属性作为key ；
     * 	    #result.name : #result代表方法返回值，该表达式 代表以返回对象的name属性作为key ；
     */
    @CachePut(value = "userCache",key="#user.id")
    @PostMapping("/cacheput")
    public User save(@RequestBody User user) {
        userService.save(user);
        return user;
    }
    /**
     * 当我们在删除数据库user表的数据的时候,我们需要删除缓存中对应的数据,
     * 此时就可以使用@CacheEvict注解, 具体的使用方式如下:
     *
     * @CacheEvict注解作用：
     *      清理指定缓存
     *      value：缓存的名称，每个缓存名称下面可以有多个key
     *      key：缓存的key----> 支持spring的表达式语言spel语法
     *
     *      1、allEntries：是否清空左右缓存。默认为false
     *      当指定了allEntries为true时，Spring Cache将忽略指定的key
     *      2、beforeInvocation：是否在方法执行前就清空，默认为 false
     *
     */
    @CacheEvict(value = "userCache",key = "#p0") // #p0代表第一个参数
    // @CacheEvict(value="userCache",key="#root.args[0]") //#root.args[0] 代表第一个参数
    // @CacheEvict(value = "userCache",key = "#id") //#id 代表变量名为id的参数
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        userService.removeById(id);
    }
    /**
     * 在 update 方法上加注解@CacheEvict
     * 在更新数据之后，数据库的数据已经发生了变更我们需要将缓存中对应的数据删除掉，
     * 避免出现数据库数据与缓存数据不一致的情况。
     *
     * @CacheEvict(value="userCache",key="#p0.id") // 第一个参数的id属性
     * @CacheEvict(value="userCache",key="#user.id") // 参数名为user参数的id属性
     * @CacheEvict(value="userCache",key="#root.args[0].id") // 第一个参数的id属性
     */
    @CacheEvict(value = "userCache",key = "#result.id") // 返回值的id属性
    @PostMapping("/update")
    public User update(@RequestBody User user) {
        userService.updateById(user);
        return user;
    }

    /**
     *  @Cacheable注解
     *      作用：在方法执行前，spring先查看缓存中是否有数据，如果有数据，则直接返回
     *      缓存数据，若没有数据，调用方法并将方法返回值放到缓存中
     *      value: 缓存的名称，每个缓存名称下面可以有多个key
     *      key：缓存的key---->支持spring的表达式语言spel语法
     *
     *      缓存非null值
     *      在@Cacheable注解中，提供了两个属性分别为：condition,unless
     *      condition: 表示满足什么条件，在进行缓存
     *      unless：表示满足条件则不缓存，与上述condition相反
     *
     *      一、其中condition是对入参进行判断，符合条件的缓存，不符合的不缓存。
     *         @Cacheable的condition 属性能使用的SpEL语言只有#root和获取参数类的SpEL表达式, 不能使用返回结果的#result 。
     *         所以 condition = "#result != null" 会导致所有对象都不进入缓存,
     *         每次操作都要经过数据库。
     *
     *         二、其中unless是对出参进行判断，符合条件的不缓存，不符合的缓存。
     *          解决方案：
     *          @Cacheable的unless属性可以使用#result表达式。
     *          效果: 缓存如果有符合要求的缓存数据则直接返回，没有则去数据库查数据，
     *          查到了就返回并且存在缓存一份，没查到就不存缓存。
     */
    @Cacheable(value = "userCache",key = "#id",unless = "#result == null")
    @GetMapping("/get/{id}")
    public User getById(@PathVariable Long id) {
        User user = userService.getById(id);
        return user;
    }

}
